package cipher.io;

import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.FileChannel;

import cipher.util.CipherConst;
import cipher.util.IFCrypt;

public class CipherFileInputStream extends InputStream {
	
	private long vfp;
	private byte dbuf[];
	private long vlen;
	private long lastDecodePos;
	private long realPos;
	private int dpos=0;
	private int dlen = 0;
	private IFCrypt cipher = null;
	private FileInputStream fi = null;
	private FileChannel channel = null;
	private CipherFile file;
	byte _tmpR[] = new byte[CipherConst.enc_kBufferSize];
	
	
	public CipherFileInputStream(CipherFile file){
		cipher = file.getCipher();
		this.file = file;
		try {
			vlen = file.length();
			fi = new FileInputStream(file.getPhysicalFile());
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public int available() throws IOException{
		return (int)(lastDecodePos -vfp);
	}
	
	@Override
	public int read() throws IOException {
		if(dbuf!=null && dpos < dlen ){
			vfp++;
		}else{
			int r=read_1(null,0,1);
			if(r==-1) return -1;
		}
		return dbuf[dpos++] & 0xFF;
	}
	
	@Override
	public int read(byte [] buf ) throws IOException{
		return read_1(buf,0,buf.length);
	}
	@Override
	public int read(byte [] buf ,int offset ,int len) throws IOException{
		return read_1(buf, offset, len);
	}
	
	private int read_1(byte [] buf ,int offset ,int len) throws IOException{ 
		long rp = vfp+len;
		if(vfp==vlen) return -1;
		if(rp > vlen){
			rp = vlen;
			len = (int)(vlen-vfp);
		}
		int orgLen = len;
		int bpos=0;
		if( dpos > 0 && dpos < dlen){
			int r = (dlen-dpos);
			int rest = r > len ? len : r;
			System.arraycopy(dbuf, dpos, buf, offset, rest);
			len -=rest;
			dpos+=rest;
			bpos+=rest;
			vfp+= rest;
		}
		int read =0;
		
		while(len>0){
			read = fi.read(_tmpR);
			if(read ==-1) {
				dlen=0;
				dpos=0;
				return -1;
			}
			realPos+=read;
			dbuf = cipher.decrypt(_tmpR, 0, read);
			if( dbuf !=null){
				dlen = dbuf.length;
				dpos=0;
				int rest = dlen > len ? len : dlen;
				if(buf!=null){
					System.arraycopy(dbuf, 0,buf,offset+bpos,rest);
					dpos+=rest;
				}
				len -=rest;
				bpos+=rest;
				vfp+= rest;
			}
		}
		return orgLen;
	}
	
	@Override
	public void close() throws IOException{
		this.fi.close();
		this.file.close();
	}
	
	
	public long skip(long n) throws IOException{
		long skip = vfp+n;
		if(skip < lastDecodePos || skip > vlen){
			vfp +=n;
			return n;
		}
		long p = CipherConst.getDecodedLastBlockPosition(skip);
		long ep = CipherConst.convert2encodedBlockPosition(p);
		long sp = ep-realPos;
		realPos+=fi.skip(sp);
		byte []ebuf = new byte[CipherConst.enc_kBufferSize];
		int read = fi.read(ebuf);
		realPos+=read;
		dbuf = cipher.decrypt(ebuf,0, read);
		dlen = dbuf.length;
		lastDecodePos=p+dlen;
		dpos= (int)(skip - p);
		vfp +=n;
		return n;
	}
	
	public FileChannel getChannel() throws IOException{
		if(channel==null)
			channel = new CipherFileChannel(fi.getChannel(), file,false, true);
		return channel;
	}
	
	public final FileDescriptor getFD() throws IOException{
		return fi.getFD();
		
	}
	
	@Override
	protected void finalize() throws Throwable {

	}
}
